El principio de substitución de Liskov nos dice que una subclase tiene que poder substituir a la clase base cuando se pasa como argumento a una función sin causar un mal funcionamiento en dicha función.
Si por ejemplo tenemos una clase Animal y una subclase Gato que deriva de animal, en una funcion como ObtenerEdad(Animal animal), da igual que le pasaemos un objeto de tipo Animal o de tipo Gato, la función debería de tener un resultado valido en ambos casos, en caso de no ser así se estaría violando el principio LSP.
Clase principal:
public class Liskov {
public static void main(String[] args) {
Circulo circulo = new Circulo();
calcularAreaForma(circulo);
}
public static void calcularAreaForma(Forma forma){
if (forma.esTipo.equals("cuadrado")) {
((Cuadrado)forma).calcularArea();
}
else if (forma.esTipo.equals("circulo")) {
((Circulo)forma).calcularArea();
}
}
}
Clase Forma:
public class Forma {
String esTipo;
}
Clase Cuadrado:
public class Cuadrado extends Forma{
Cuadrado(){
esTipo = "cuadrado";
}
public void calcularArea(){
System.out.println("area cuadrado");
}
}
Clase Circulo:
public class Circulo extends Forma {
Circulo() {
esTipo = "circulo";
}
public void calcularArea(){
System.out.println("Area Circulo");
}
}
En este ejemplo en la funcion calcularAreaForma no podemos hacer la sustitución de circulo o cuadrado por forma lo que nos lleva a que se está violando el LSP, ya que la función calcularArea está definida solo en las subclases y no en la clase base como función abstracta, por lo tanto lo que se hace para poder utilizar las clases circulo y cuadrado es utilizar una cadena de if/else para determinar que tipo de objeto se está pasando y llamar a la función de cada subclase, pero esto nos lleva a que estamos violando el OCP.
La solución en este caso es definir la función calcularArea en la clase base como función abstracta, lo que nos permite cumplir tanto con el LSP como con el OCP
Clase principal:
public class Liskov {
public static void main(String[] args) {
Circulo circulo = new Circulo();
calcularAreaForma(circulo);
}
public static void calcularAreaForma(Forma forma){
forma.calcularArea();
}
}
Clase Forma:
public abstract class Forma {
protected abstract void calcularArea();
}
Clase Circulo:
public class Circulo extends Forma {
public void calcularArea(){
System.out.println("Area Circulo");
}
}
Clase Cuadrdado:
public class Cuadrado extends Forma {
public void calcularArea(){
System.out.println("area cuadrado");
}
}
Cuando derivamos una subclase para saber si la abstracción que estamos creando es correcta podemos usar la relación "is a" (en español "es un"), para saber si nuestro diseño tiene sentido en principio...
Este metodo se basa en lo siguiente: si por ejemplo queremos crear una subclase de Animal, podemos decir que Pato "es un" Animal, siempre que se cumpla esta realación la abstracción tiene coherencia, por ejemplo en el caso de una clase coche si queremos crear una subclase motor, NO podemos decir motor "es un" coche, por lo tanto en este caso no tendría sentido crear dicha subclase porque probablemente estemos usando una abstacción incorrecta.
Design by contract implica que los metodos de una clase tienen que cumplir unas precondiciones y unas postcondiiciones.
Los métodos de una subclase tienen que cumplir las condiciones de la siguiente manera:
SOLID | Liskov Substitution